/**
 * @author alex
 */

Namespace("sketch.framework");


(function($) {
    var fwk = sketch.framework,
        dec = sketch.decorators;

    fwk.View = klass(null, (function() {

        var updateEventAccordingToScale = function(event){
            var scale = this.scale();
            event.layerX /= scale;
            event.layerY /= scale;
        };

        var onmousedown = function(event) {
            updateEventAccordingToScale.call(this, event);
            var eventData = { handled:false, x:event.layerX, y:event.layerY };
            var that = this;

            this._noActionsBeforeClick = true;

            if (this._captureElement !== null) {
                this._captureElement.mousedown(eventData);
                return;
            }

            this._layersReverse.each(function(i, layer) {
                layer.mousedown(eventData);
                if (eventData.handled) {
                    return false;
                }
            });

            // apply default behavior
            if (!eventData.handled) {
                this._layersReverse.each(function(i, layer) {
                    var element = layer.hitElement(eventData);
                    if (element !== null) {
                        eventData.element = element;
                        that._startDraggingData = eventData;
                        eventData.handled = true;
                        return false;
                    }
                });
            }

            if (!eventData.handled) {
                this._selectFrame = new fwk.SelectFrame(EventHandler(this, onselect));
                this._selectFrame.init(eventData);
                this.layer3.add(this._selectFrame);
            }
        };

        var onmouseup = function(event) {
            updateEventAccordingToScale.call(this, event);
            var eventData = { handled:false, x:event.layerX, y:event.layerY, element:this._draggingElement };

            this._startDraggingData = null;

            if (this._captureElement !== null) {
                this._captureElement.mouseup(eventData);
                return;
            }

            if (this._selectFrame !== null) {
                this._selectFrame.complete(eventData);
                this.layer3.remove(this._selectFrame);
                this._selectFrame = null;
                return;
            }

            if (this._draggingElement !== null) {
                stopDrag.call(this, eventData);
                this._draggingElement = null;
            } else {
                this._layersReverse.each(function(i, layer) {
                    layer.mouseup(eventData);
                    if (eventData.handled) {
                        return false;
                    }
                });
            }
        };

        var onmousemove = function(event) {
            updateEventAccordingToScale.call(this, event);
            var x = event.layerX,
                y = event.layerY;
            var eventData = { handled:false, x:x, y:y };
            var that = this;

            this._noActionsBeforeClick = false;

            // this is needed for delayed dragging (not create dragging element before we move mouse)
            if(this._startDraggingData){
                beginDrag.call(this, this._startDraggingData);
                this._startDraggingData = null;
            }

            if (this._captureElement !== null) {
                this._captureElement.mousemove(eventData);
                return;
            }

            if (this._selectFrame !== null) {
                this._selectFrame.update(eventData);
                return;
            }

            if (this._draggingElement !== null) {
                dragging.call(this, {
                    x : (x - this._draggingOffset.x),
                    y : (y - this._draggingOffset.y),
                    mouseX : x,
                    mouseY : y
                });

                this.invalidate();
                return;
            }

            // change cursor
            var cursorChanged = false;
            this._layersReverse.each(function(i, layer) {
                    var element = layer.hitElement(eventData);
                    if (element !== null) {
                        that.setCursor(element.cursor());
                        cursorChanged = true;
                        return false;
                    }
                });

            if (!cursorChanged) {
                this.setCursor(null);
            }

            this._layersReverse.each(function(i, layer) {
                layer.mousemove(eventData);
                if (eventData.handled) {
                    return false;
                }
            });
        };

        var ondblclick = function(event) {
            updateEventAccordingToScale.call(this, event);
            var eventData = { handled:false, x:event.layerX, y:event.layerY };
            this._layersReverse.each(function(i, layer) {
                layer.dblclick(eventData);
                if (eventData.handled) {
                    return false;
                }
            });
        };

        var onclick = function(event) {
            updateEventAccordingToScale.call(this, event);
            var eventData = { handled:false, x:event.layerX, y:event.layerY };
            if (this._noActionsBeforeClick) {
                var element = this.layer2.hitElement(eventData);

                if (element !== null) {
                    eventData.element = element;
                    this.onElementClicked(eventData);
                    if (eventData.handled){
                        return;
                    }

                    this.makeSelection([element]);
                    element.click(eventData);
                }
            }
        };

        var setupProperties = function() {
            this.properties = new fwk.Properties();
            this.properties.createProperty("width", "View width", 0);
            this.properties.createProperty("height", "View height", 0);
            this.properties.createProperty("left", "View left position", 0);
            this.properties.createProperty("top", "View top position", 0);
            this.properties.createProperty("scale", "View scale", 1);
        };

        var setupLayers = function() {
            var that = this;
            this.layer1 = new fwk.Layer(false);
            this.layer1.setContext(this.context);
            this.layer1.__id__ = "Layer1";
            this.layer2 = new fwk.Layer(false);
            this.layer2.setContext(this.context);
            this.layer2.__id__ = "Layer2";
            this.layer3 = new fwk.Layer(false);
            this.layer3.setContext(this.upperContext);
            this.layer3.__id__ = "Layer3";
            this.layer3.add(this._selectCompositeElement);
            this._layers = $([this.layer1, this.layer2, this.layer3]);
            this._layersReverse = $([this.layer3, this.layer2, this.layer1]);
            this._layers.each(function(i, layer) {
                layer.parent(that);
            });
        };

        var onselect = function(rect) {
            var selection = [];

            this.layer2.applyVisitor(function(element) {
                if (element.left() >= rect.x && element.top() >= rect.y && element.right() <= rect.x + rect.width && element.bottom() <= rect.y + rect.height) {
                    selection.push(element);
                }
            });

            this.makeSelection(selection);
        };

        var ensureCanvasSize = function() {
            var width = this.canvas.parentNode.offsetWidth;
            var height = this.canvas.parentNode.offsetHeight;
            var resized = false;
            if (this.canvas.width !== width) {
                this.canvas.width = width;
                this.upperCanvas.width = width;
                this.width(width);
                resized = true;
            }
            if (this.canvas.height !== height) {
                this.upperCanvas.height = height;
                this.canvas.height = height;
                this.height(height);
                resized = true;
            }

            if (resized) {
                var rect = {
                    x:0,
                    y:0,
                    width:width,
                    height:height
                };
                this._layers.each(function(i, layer) {
                    layer.resize(rect);
                });
            }
        };

        var stopDrag = function(event) {
            var draggingElement = this._draggingElement.element();
            var eventData = {
                handled:false,
                element:draggingElement,
                x:event.x,
                y:event.y
            };
            var that = this;

            this.layer2.applyVisitor(function(element) {
                var isSelf = false;
                if (draggingElement !== that._selectCompositeElement){
                    isSelf = element.isDescendantOrSame(draggingElement);
                }
                else{
                    that._selectCompositeElement.each(function(e){
                        isSelf = element.isDescendantOrSame(e);
                        return !isSelf;
                    });
                }

                if (!isSelf) {
                    var point = element.parent().global2local(event);
                    if(element.hitTest(point.x, point.y) && element.canAccept(draggingElement)) {
                        that._draggingElement.detach(eventData, element);
                        if (draggingElement !== that._selectCompositeElement) {
                            element.dropOn(eventData);
                        }
                        return false;
                    }
                }
            });

            this.layer2.applyVisitor(function(element) {
                if (this._draggingElement !== element) {
                    element.stopDragging(eventData);
                }
            });

            if (this._draggingElement !== null) {
                this._draggingElement.stopDrag({
                    mouseX : event.mouseX,
                    mouseY : event.mouseY
                });
            }

            this.stopDraggingEvent(eventData);
        };

        var beginDrag = function(event) {
            var element = event.element;
            var elementStartPosition = element.position();
            this._draggingOffset = {
                x:event.x - elementStartPosition.x,
                y:event.y - elementStartPosition.y
            };
            var eventData = {
                mouseX : event.x,
                mouseY : event.y,
                x : event.x - this._draggingOffset.x,
                y : event.y - this._draggingOffset.y
            };

            if (element.startDrag(eventData) !== false) {
                this._draggingElement = new fwk.DraggingElement(element);
                this.layer3.add(this._draggingElement);
                this._draggingOffset = {
                    x:event.x - this._draggingElement.left(),
                    y:event.y - this._draggingElement.top()
                };

                this.layer2.applyVisitor(function(element) {
                    element.startDragging(eventData);
                });

            } else {
                this._draggingElement = null;
            }
        };

        var dragging = function(event) {
            var that = this;
            var eventData = {
                handled:false,
                element:this._draggingElement.element(),
                x:event.x,
                y:event.y,
                mouseX:event.mouseX,
                mouseY:event.mouseY,
                target: this._draggingOverElement
            };

            if (this._draggingOverElement != null &&
                !this._draggingOverElement.hitTest(event.mouseX, event.mouseY)) {
                this._draggingOverElement.draggingLeft(eventData);
                this.draggingLeftEvent(eventData);
                this._draggingOverElement = null;
            }

            var mousePoint = {x:eventData.mouseX, y:eventData.mouseY};
            this.layer2.applyVisitor(function(element) {
                if (!element.isDescendantOrSame(eventData.element)) {
                    var point = element.parent().global2local(mousePoint);
                    if (element.hitTest(point.x, point.y) && element.canAccept(eventData.element)) {
                        eventData.target = that._draggingOverElement = element;
                        that._draggingOverElement.draggingEnter(eventData);
                        that.draggingEnterEvent(eventData);
                        return false;
                    }
                }
            });

            this.layer2.applyVisitor(function(element) {
                if (that._draggingElement !== element) {
                    element.dragging(eventData);
                    that.draggingEvent(eventData);
                }
            });

            this._draggingElement.dragTo(eventData);
        };

        // debug info

        var lastTick = 0;
        var frames = 0;
        var fps = 0;
        var getFpsCount = function() {
            var date = new Date();
            var tick = date.getTime();
            frames++;
            if (tick - lastTick > 1000) {
                fps = frames;
                frames = 0;
                lastTick = tick;
            }

            return fps;
        };

        var iterateElements = function(element, visitorFunc) {
            visitorFunc(element);

            if (element.__type__ == "Container") {
                element.children.each(function(idx, child) {
                    iterateElements(child, visitorFunc);
                    return true;
                });
            }
        };

        // public methods
        return {
            _constructor:function(/*HtmlElement*/parentElement){
                var that = this;
                this.parent = parentElement; // parent div element
                this.canvas = document.createElement("canvas");
                this.canvas.setAttribute("style", "position:absolute;top:0;left:0");
                this.upperCanvas = document.createElement("canvas");
                this.upperCanvas.setAttribute("style", "position:absolute;top:0;left:0");
                this.parent.appendChild(this.canvas);
                this.parent.appendChild(this.upperCanvas);
                this.context = this.canvas.getContext("2d");
                this.upperContext = this.upperCanvas.getContext("2d");
                this._draggingElement = null;
                this._selectFrame = null;
                this._selectCompositeElement = new fwk.SelectComposite();
                this._captureElement = null;
                this._draggingOverElement = null;
                this._startDraggingData = null; // delayed dragging data
                this._noActionsBeforeClick = false;
                this._selectionMode = "new"; //"add", "remove"

                setupProperties.call(this);
                setupLayers.call(this);

                parentElement = $(parentElement);
                parentElement.bind('mousedown', EventHandler(this, onmousedown));
                parentElement.bind('mousemove', EventHandler(this, onmousemove));
                parentElement.bind('mouseup', EventHandler(this, onmouseup));
                parentElement.bind('dblclick', EventHandler(this, ondblclick));
                parentElement.bind('click', EventHandler(this, onclick));

                this.draggingEvent = fwk.EventHelper.createEvent();
                this.draggingEnterEvent = fwk.EventHelper.createEvent();
                this.draggingLeftEvent = fwk.EventHelper.createEvent();
                this.stopDraggingEvent = fwk.EventHelper.createEvent();

                this.onElementSelected = fwk.EventHelper.createEvent();
                this.onElementClicked = fwk.EventHelper.createEvent();

                this._dropVisualization = new fwk.DropVisualization();
                this.clear();
            },
            draw:function() {
                var scale = this.scale();
                this.context.save();
                this.context.scale(scale, scale);

                if (this.layer2.isInvalidateRequired()) {
                    var that = this;
                    ensureCanvasSize.call(this);
                    this.context.clearRect(0, 0, this.width(), this.height());
                    this.layer2.draw(this.context);
                }
                this.context.restore();

                this.upperContext.save();
                this.upperContext.scale(scale, scale);
                if (this.layer3.isInvalidateRequired()) {
                    this.upperContext.clearRect(0, 0, this.width(), this.height());
                    this.layer3.draw(this.upperContext);
                    var fps = getFpsCount();
                    this.upperContext.fillText("FPS: " + fps, 0, 15);
                }
                this.upperContext.restore();

            },
            left : function(/*int*/value) {
                return this.properties.left(value);
            },
            top : function(/*int*/value) {
                return this.properties.top(value);
            },
            width : function (/*int*/value) {
                return this.properties.width(value);
            },
            height : function (/*int*/value) {
                return this.properties.height(value);
            },
            scale: function (/*int*/value) {
                return this.properties.scale(value);
            },
            view : function () {
                return this;
            },
            addHtmlElement : function(/*HtmlElement*/element) {
                this.parent.appendChild(element);
            },
            removeHtmlElement : function(/*HtmlElement*/element) {
                this.parent.removeChild(element);
            },
            captureMouse : function(/*UIElement*/element) {
                this._captureElement = element;
            },
            releaseMouse : function(/*UIElement*/element) {
                this._captureElement = null;
            },
            global2local : function(/*Point*/pos) {
                return pos;
            },
            local2global : function(/*Point*/pos) {
                return pos;
            },
            generateModel : function() {
                var model = {};
                model.type = "View";
                var parent = model;
                iterateElements(this.layer2, function(element) {
                    var current = {};
                    current.type = element.__type__;
                    current.properties = element.properties.getModelProperties();

                    if (parent.children === undefined) {
                        parent.children = [];
                    }
                    parent.children.push(current);

                    parent = current;
                });
                return model;
            },
            clickAt: function(x, y) {
                var event = $.Event('mousedown');
                event.layerX = x;
                event.layerY = y;
                $(this.parent).trigger(event);

                event = $.Event('mouseup');
                event.layerX = x;
                event.layerY = y;
                $(this.parent).trigger(event);
            },
            invalidate:function(layer) {
                if (layer === undefined) {
                    this._layers.each(function(_, layer) {
                        layer.invalidate();
                    });
                }
                else {
                    this._layers[layer].invalidate();
                }
            },
            parent:function() {
                return null;
            },
            isElementSelected:function(el) {
                return this._selectCompositeElement.has(el);
            },
            selectedElement:function(){
                if (this._selectCompositeElement.count() === 0){
                    return null;
                }
                else if (this._selectCompositeElement.count() === 1){
                    return this._selectCompositeElement.elementAt(0);
                }
                return this._selectCompositeElement;
            },
            locked:function(){
                return false;
            },
            selectionMode: function(value){
                if (arguments.length === 1){
                    if (value !== "new" && value !== "add" && value !== "remove") {
                        throw {name: "ArgumentException", message: "Incorrect selection mode specified"};
                    }
                    this._selectionMode = value;
                }
                return this._selectionMode;
            },
            addToSelection: function(/*Array*/elements){
                var selection = this.decomposeSelection(elements);
                selection.sort(function(a, b){ return a.top() - b.top() });

                for (var i = 0, j = selection.length; i < j; ++i) {
                    this._selectCompositeElement.add(selection[i]);
                }

                this._selectCompositeElement.selected(false);
                this._selectCompositeElement.selected(true);
            },
            getSelection: function(){
                var selectedElement = this.selectedElement();
                if (!selectedElement) {
                    return null;
                }
                return this.decomposeSelection([selectedElement]);
            },
            decomposeSelection: function(selection){
                if (!selection){
                    return [];
                }

                var newSelection = selection;

                if (selection.length === 1) {
                    var selectedElement = selection[0];

                    if (selectedElement instanceof fwk.SelectComposite) {
                        newSelection = [];
                        selectedElement.each(function(element){
                            newSelection.push(element);
                        });
                    }
                }
                return newSelection;
            },
            makeSelection: function(selection){
                if (!selection || selection.length === 0){
                    this.unselectAll();
                }
                else if (this._selectionMode === "add"){
                    this.addToSelection(selection);
                }
                else if (this._selectionMode === "new"){
                    var currentSelection = this._selectCompositeElement.elements;
                    var newSelection = this.decomposeSelection(selection);

                    if (areSameArrays(currentSelection, newSelection)){
                        return;
                    }

                    this.unselectAll();
                    this.addToSelection(selection);
                }

                this.fireOnElementSelected();
            },
            fireOnElementSelected: function(){
                if (this._selectCompositeElement.count() > 1) {
                    this.onElementSelected(this._selectCompositeElement);
                }
                else if (this._selectCompositeElement.count() === 1){
                    this.onElementSelected(this._selectCompositeElement.elementAt(0));
                }
                else {
                    this.onElementSelected(null);
                }
            },

            unselectAll: function() {
                this._selectCompositeElement.selected(false);
                this._selectCompositeElement.clear();
            },

            clear:function(){
                this._selectCompositeElement.clear();

                this.layer2.clear();
                this.layer3.clear();

                // add service elements
                this.layer3.add(this._dropVisualization);
                this.layer3.add(this._selectCompositeElement);
            },

            setCursor:function(value){
                if(this._cursor != value){
                    this._cursor = value;
                    this.upperCanvas.style.cursor = value;
                }
            }
        };
    })());

})(jQuery);